Blog Top          Mijinko Semi Top

Mijinko seminar. Produced By SP

Now the Mijinko it is possible to stand up!!('A`)

	

多重起動3−3(EnumWindows、その他の多重起動について)

	無事にここまで辿りつけたか?
	
	00401000   . 8B4C24 04      MOV ECX,DWORD PTR SS:[ESP+4]
	
	大丈夫そうだな…。
	
	それでは続きを始める。
	いま俺たちがいるのはコールバック関数の始まりの場所だ。
	何度も言っているが、コールバック関数の中は好きな処理を書くことが出来る。
	
	それとコールバック関数の引数はまだ覚えているか?
	一つは呼ばれるたびにデスクトップ上に存在するウィンドウのウィンドウハンドルが渡される hwnd、
	もう一つは EnumWindows で指定した値が渡される lParam だ。
	ほかには hwnd の値を元に別の API を呼び出しウィンドウタイトルを取得していることも話したな。
	
	・・・ん?
	
	おいおい、まてまて。 まだ動くんじゃない。
	先に F2 でブレークポイントを仕掛けてから一回 F9 をしておけ。
	今おれたちは EnumWindows のところから飛んできたがあくまで先回りして飛んできただけだ。
	つまりここにくるまでの処理はしていないわけだ。
	それと今回も狙いは多重起動の回避だ、まだサンプルプログラムを起動させてないよな?
	それなら起動し「OK!」のメッセージボックスの状態で止めておくんだぞ。
	
	出来たか?それでは先に進むぞ。
	まず最初に目につくのが GetWindowTextA API であるのは言うまでも無いな。
	
	0040100B   . 6A 32          PUSH 32                                  ; /Count = 32 (50.)
	0040100D   . 50             PUSH EAX                                 ; |Buffer
	0040100E   . 51             PUSH ECX                                 ; |hWnd
	0040100F   . FF15 98404000  CALL DWORD PTR DS:[<&USER32.GetWindowTex>; \GetWindowTextA
	
	それでは早速 MSDN で見てみるとするか。
	
	
	
	-- MSDN より抜粋 --
	
	GetWindowText
	指定されたウィンドウのタイトルバーのテキストをバッファへコピーします。
	指定されたウィンドウがコントロールの場合は、コントロールのテキストをコピーします。
	ただし、他のアプリケーションのコントロールのテキストを取得することはできません。
	
	int GetWindowText
	 HWND hWnd,        // ウィンドウまたはコントロールのハンドル
	  LPTSTR lpString,  // テキストバッファ
	  int nMaxCount     // コピーする最大文字数
	);
	
	パラメータ
	hWnd 
	ウィンドウ( またはテキストを持つコントロール)のハンドルを指定します。 
	lpString 
	バッファへのポインタを指定します。このバッファにテキストが格納されます。 
	nMaxCount 
	バッファにコピーする文字の最大数を指定します。
	テキストのこのサイズを超える部分は、切り捨てられます。NULL 文字も数に含められます。 
	
	戻り値
	関数が成功すると、コピーされた文字列の文字数が返ります( 終端の NULL 文字は含められません)。
	タイトルバーやテキストがない場合、タイトルバーが空の場合、および hWnd パラメータに指定した
	ウィンドウハンドルまたはコントロールハンドルが無効な場合は 0 が返ります。
	拡張エラー情報を取得するには、GetLastError 関数を使います。

	他のアプリケーションのエディットコントロールのテキストをこの関数で取得することはできません。

	-------------------
	
	もう分かったと思うが、こいつがコールバック関数の引数 hwnd を用いて、対応するウィンドウの
	ウィンドウタイトルを取得する API の正体だ。
	引数については説明の必要が無いかもしれんが、一応書いておこう。
	
	この API に必要な引数は以下の3つだ。
	
	hWnd
	lpString
	nMaxCount
	
	まず hWnd だが、こいつには任意のウィンドウハンドルを指定すればいい。
	今回のケースでいえばコールバック関数の引数 hwnd の値を渡すこととなる。
	次に lpstring だが、ターゲットのウィンドウタイトルを保存する変数のポインタ(場所)を指定する。
	最後の nMaxCount だが、ここには保存するウィンドウタイトルの最大長を指定する。
	例えばウィンドウタイトルが10文字である場合に、ここを9とすると10−9、つまり1文字足りない状態で
	lpString にウィンドウタイトルが保存されることになる。
	まぁ、これもプログラムを作る側の問題だから俺たちは特に意識する必要はないな。
	
	最後に戻り値となるが、これはそのままの意味だ。コピーできたウィンドウタイトルの文字数が渡されることとなる。
	
	さて、ウィンドウタイトルを取得している部分の仕組みはわかった。
	次はどうやって多重起動のチェックをしているか?であるが、ウィンドウタイトルを取得する処理があることから
	取得したウィンドウタイトルと任意の文字列を比較し、多重起動のチェックをしているのは間違いないな。
	つまり FindWindows の時と同じく決められたウィンドウタイトルを持つウィンドウがあるかどうか?をチェックしていると考えられる。
	
	さて、話を戻して解析を進めるとしよう。
	すぐ下の処理をみると test 命令により値のチェックをしているのが目に付くが、一般的に test 命令は
	指定した比較対象に値が存在するかどうか?空ではないか? を確認する命令である。
	これと GetWindowText の関連を考えると、ウィンドウタイトルが取得できたかどうかくらいのチェックしてないと考えられるな。
			
	00401019   . 84C0           TEST AL,AL
	0040101B   . 74 58          JE SHORT EnumWind.00401075
	
	ではさらに下を見てみようか。
	
	0040101D   . 53             PUSH EBX
	0040101E   . 56             PUSH ESI
	0040101F   . BE 3C504000    MOV ESI,EnumWind.0040503C             ;  ASCII "EnumWindows"
	
	ここで EnumWindows の文字列見えるな。移動先は ESI だ。
	更に下を見ると LEA 命令があるが、どんなデータを移動させているのだろうか?
	早速ブレイクポイントを仕掛け実行してみるとしよう。
	
	00401024   . 8D4424 08      LEA EAX,DWORD PTR SS:[ESP+8]
	
	この命令で移動する値は ESP + 8 のアドレスにある値だ。
	CPUウィンドウのすぐ下を見ればどんな値が入っているかすぐに分かるが、念のためレジスタウィンドウも確認しておこう。
	
	レジスタウィンドウでは ESP は 12FEB0 を指している。この値に + 8 をすると見事に 12FEB8 となる。
	つまり以下の内容で参照をおこなっているわけだ。
		
	スタック アドレス = 0012FEB8, (ASCII "CiceroUIWndFrame")
	EAX=00000043

	だが・・・この "CiceroUIWndFrame" とはなんであろうか?
	しばらく F9 を押して様子を見てみるか。
	
	
	・・・
	
	
	ん? EnumWindows の文字が入ってきたぞ!?
	
	スタック アドレス = 0012FEB8, (ASCII "EnumWindows")
	
	一つ上で ESI に EnumWindows の文字列を渡していたはずだが、なぜ EAX に…
	そうか、GetWindowText により確認されたウィドウタイトルが都度 EAX に渡されているわけか。
	そして少し下にはいくつかの比較命令が見えている。
	
	00401028   > 8A10           MOV DL,BYTE PTR DS:[EAX]
	0040102A   . 8A1E           MOV BL,BYTE PTR DS:[ESI]
	0040102C   . 8ACA           MOV CL,DL
	0040102E   . 3AD3           CMP DL,BL
	00401030   . 75 1E          JNZ SHORT EnumWind.00401050
	00401032   . 84C9           TEST CL,CL
	00401034   . 74 16          JE SHORT EnumWind.0040104C
	
	怪しいな、F8 で一つずつ進めてみるとしよう。
	まずは MOV で EAX が指すアドレスに格納されている 値を DL に渡している。
	先頭に BYTE とあることから 1Byte、つまり1文字分を DL に格納している。
	EAX には EnumWindows の文字列が入ったばかりだ、つまり DL に先頭文字の E を渡している。
		
	00401028   > 8A10           MOV DL,BYTE PTR DS:[EAX]
	
	次に ESI も同じ処理をしているな。ESI にも EnumWindows の値が入っている。
	そして 1Byte の移動、BL にも E が渡される。
	
	0040102A   . 8A1E           MOV BL,BYTE PTR DS:[ESI]
	
	更に CL へ値を渡す。
	
	0040102C   . 8ACA           MOV CL,DL
	
	ん?混乱してきたか?
	面倒くさいが、EAX、ESI、DL、BL それぞれのレジスタに入っている値を教えてやる。
		
	EAX ⇒ EnumWindows
	ESI ⇒ EnumWindows
	DL  ⇒ E
	BL	⇒ E
	CL  ⇒ E
	
	進めるぞ、つぎは CMP による比較命令だ。
	
	0040102E   . 3AD3           CMP DL,BL

	この場合、DL と BL は同じ文字同士であるためゼロフラグが立つ。
	そしてこれは次の JNZ の判断に使われているようだな。
	
	00401030   . 75 1E          JNZ SHORT EnumWind.00401050
	
	JNZ はゼロフラグが立たない時に指定先へ飛ばす命令だ。
	よって今回は飛ばずに処理を進める。
	
	次はまた TEST 命令であるが、CL には E が入っている。
	よって空ではないのでゼロフラグは立たない。
	
	00401032   . 84C9           TEST CL,CL
	
	このような繰り返しがしばらく続くが、いづれ「だめぷー」のメッセージを作成している箇所に
	当たるだろう。
	
	00401057   . 85C0           TEST EAX,EAX
	00401059   . 75 1A          JNZ SHORT EnumWind.00401075
	0040105B   . 50             PUSH EAX                                 ; /Style
	0040105C   . 8D4424 04      LEA EAX,DWORD PTR SS:[ESP+4]             ; |
	00401060   . 50             PUSH EAX                                 ; |Title
	00401061   . 68 30504000    PUSH EnumWind.00405030                   ; |Text = "だめぷー"
	00401066   . 6A 00          PUSH 0                                   ; |hOwner = NULL
	00401068   . FF15 9C404000  CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
	
	さて、ここにある TEST、JNZ が最終的な判断をする判断文であると推測されるが
	JNZ で飛ばずに進んでしまえばこのメッセージが表示されることになるのは明白だ。
	ならばこの JNZ で飛んでしまったらどうなるのだろうか?
	
	では早速試してみよう。
	JNZ 命令で一度停止し、レジスタウィンドウの Z フラグをクリックしゼロに変えるだけだ。
	
	Z 0
	
	これが出来たら F9 で処理をさせてみろ。
	
	さて、どこへ飛んだかわかるか?
	
	00401024   . 8D4424 08      LEA EAX,DWORD PTR SS:[ESP+8]
	
	そうだ、ここは先ほど EnunWindows の値が EAX に格納されたのを確認したあの命令文だ。
	これはどういうことだろうか?
	
	今回はこの EAX にウィンドウタイトルである EnumWindows が入った場合として解析を進めてきた。
	その結果、さきほどの「だめぷー」のメッセージボックスに突き当たり、本来ならあのメッセージボックスが
	表示されるはずだった。
	つまりあそこで 多重起動 のチェックがされているわけだ。
	このことから EAX に EnumWindows 以外の文字列が入っている場合は、さきほどの JNZ 命令で
	ジャンプし、また新しいウィンドウタイトルを EAX に格納し処理を繰り返すということでもある。
	
	もう回避方法は分かったな?
	
	よし、今回は俺からの回答は無しだ。
	自分で好きなように書き換えてみろ。
	
	また、解析をしていく上でお前たちにいくつかの疑問点が残ったのではないだろうか。
	
	今回俺が教えたかったのは多重起動の回避方法である。が、もう一つ狙いがある。
	
	「考える」ということだ。
	
	いままでは手引きをしながら進めていったが、実践ではそうはいかない。
	そして「考える」とは解析をする上で非常に重要な仕事だ。
	
	それと今回のサンプルプログラムを作ったのは俺だが、事前に隅々まで解析はしていない。
	この記事を執筆しながらこのような手順で解析を行っただけだ。
	つまり、EnumWindows の文字列が入ったあたりからは一つ一つの命令を解析せずに一気に「だめぷー」の
	メッセージボックスが表示されるまでひたすら F8 で飛ばしている。
	
	もしお前たちのなかに一つ一つを確認しながら解析したものがいたならば、その結果を教えて欲しい。
	そして俺が記載した解析内容に誤りがあれば是非とも指摘してくれることを楽しみにしている。
	
	さて、多重起動については今回が最後の授業となるが最初の頃に伝えたように他にも様々な方法がある。
	例えば CreateSemaphore を使用したチェック、CreateToolHelp32Snapshot を使ったチェック、
	多少変わった方法では SendMessage による確認も可能だ。
	
	まぁ、そのほかのチェック方法、回避方法については苦労しながら勝手に考えてみろ。
	いいな?
	
	おっと、もうこんな時間か…
	それでは次回からは新しい分野、ウィンドウについての学習をするわけだがこの教室だとちと狭いな…
	
	よし、明日からは臨時教室で授業をやることにする。間違えてこっちの教室に来たりするなよ!
	
	
	
	

Blog Top    Mijinko Semi Top

Copyright (C) 2006 SP-.SEESAA.NET - All Rights Reserved.